package com.hrw.goglelocal;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;

import org.apache.poi.hssf.extractor.ExcelExtractor;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

public class SearchingMachine {

	public static HashMap<String, Boolean> ignoredWords = new HashMap<String, Boolean>();

	public enum PatternMethod {
		KMP, BM, JAVA, RK
	}

	public static void main(String args[]) {
		// initialize ignored words dictionary
		ignoredWords.put("dari", false);
		ignoredWords.put("di", false);
		ignoredWords.put("ke", false);
		ignoredWords.put("yang", false);
		ignoredWords.put("dan", false);
		ignoredWords.put("atau", false);
		ignoredWords.put("dengan", false);
		ignoredWords.put("bahwa", false);
		ignoredWords.put("ataupun", false);
		ignoredWords.put("bagaimanapun", false);
		ignoredWords.put("meskipun", false);
		ignoredWords.put("darimana", false);
		ignoredWords.put("mana", false);

		HashMap<String, Boolean> keyMap = new HashMap<String, Boolean>(); // for testing of unique key

		// example java Main BM DFS 1 path # teks yang dicari

		String searchPattern = args[0]; // BM, KMP
		String searchAlgorithm = args[1]; // BFS, DFS
		String searchContent = args[2]; // 1 -> title + content , 0 -> title
										// only

		StringBuilder pathBuilder = new StringBuilder();
		int p = 3;
		while (p < args.length && !args[p].equals("#")) {
			pathBuilder.append(args[p] + " ");
			p++;
		}
		pathBuilder.deleteCharAt(pathBuilder.lastIndexOf(" "));
		p++;
		// validation and formatting the search keys

		StringBuilder keysBuilder = new StringBuilder();
		for (int i = p; i < args.length; i++) {
			if (filterKeySearch(args[i])) {
				StringTokenizer st = new StringTokenizer(args[i],
						" .,~!@#$%^&*()"); // ignored tanda baca
				while (st.hasMoreTokens()) {
					String temp = st.nextToken().toLowerCase();
					if (!keyMap.containsKey(temp)) {
						keyMap.put(temp, false);
						keysBuilder.append(temp + " ");
					}
				}
			}
		}
		
		String searchKeys = keysBuilder.toString().toLowerCase(); // formatted search keys			

		if (searchKeys.length()>0){
			
			PatternMethod searchPatternMethod;
			// choosing method -> Bayer Moore or KMP
			if (searchPattern.equals("BM")) {
				searchPatternMethod = PatternMethod.BM;
			} else if (searchPattern.equals("KMP")) {
				searchPatternMethod = PatternMethod.KMP;
			}else if (searchPattern.equals("RK")){
				searchPatternMethod = PatternMethod.RK;
			}else {
				searchPatternMethod = PatternMethod.BM;
			}
	
			// is check the content of the file ?
			boolean isSearchContent = false;
			if (searchContent.equals("1")) {
				isSearchContent = true;
			} else if (searchContent.equals("0")) {
				isSearchContent = false;
			}else{
				isSearchContent = false;
			}
	
			// make the file to initialize the searching directory
			File file = new File(pathBuilder.toString());
	
			if (file.exists()) {
				// if directory exists
				// save the results here
				try{
					ArrayList<SearchResult> listOfResults = new ArrayList<SearchResult>();
					
					double startTime ,endTime,searchTime;
					
					startTime = System.currentTimeMillis();
	
	
					if (searchAlgorithm.equals("BFS")) {				
						findInDirectoryByBFS(searchKeys, file, isSearchContent,
								searchPatternMethod, listOfResults, false);
					} else if (searchAlgorithm.equals("DFS")) {
						findInDirectoryByDFS(searchKeys, file, isSearchContent,
								searchPatternMethod, listOfResults, false);
					} else {
						findInDirectoryByDFS(searchKeys, file, isSearchContent,
								searchPatternMethod, listOfResults, false);
					}
				
					
					endTime = System.currentTimeMillis();
					searchTime = endTime - startTime;
					// output the result
					/*
					 * jumlah dapet waktu pencarian # title & path ! preview # title &
					 * path ! preview
					 */
		
					System.out.println(listOfResults.size()); // results size
					System.out.println((searchTime / 1000)); // time spent for searching
		
					if (listOfResults.size() == 0) {
						// nothing found
						System.out.println("Nothing found");
					} else {
						// found something
						for (SearchResult s : listOfResults) {
							if (isSearchContent) {
								// show the title and the preview
								setPreview(s);
								System.out.println("#");
								System.out.println(getFileName(s.file));
								System.out.println("&");
								System.out.println(s.file);
								System.out.println("!");
								System.out.println(s.preview);
							} else {
								// only shows the title
								System.out.println("#");
								System.out.println(getFileName(s.file));
								System.out.println("&");
								System.out.println(s.file);
								System.out.println("!");
								System.out.println("--------------");
							}
						}
					}
				}catch (Exception e) {
					System.out.println("0");
					System.out.println("0");
					System.out.println("Searching Failed");				
				}
			} else {
				// directory not exists
				System.out.println("0");
				System.out.println("0");
				System.out.println("Directory not exist");
			}
		}else{
			System.out.println("0");
			System.out.println("0");
			System.out.println("Nothing to search");
		}
	}

	public static void findInDirectoryByBFS(String key, File file,
			boolean contentChecked, PatternMethod method,
			ArrayList<SearchResult> results, boolean fullSearch) {

		// search string in files in directory by BFS method
		LinkedList<File> queue = new LinkedList<File>();
		queue.add(file);

		while (!queue.isEmpty()) {
			File currentFile = queue.removeFirst();
			if (currentFile.isFile()) {
				if (filterExtension(currentFile)) {
//					System.out.println(currentFile);
					if (!contentChecked) {
						// check only the title								
						SearchResult searchResult = findKeysInTitle(key,
								currentFile, method);
						if (searchResult.isFound) {
							results.add(searchResult);
						}
					} else {
						// check title + content
						SearchResult searchResult = findKeysInTitle(key,
								currentFile, method);
						SearchResult contentResult = findKeysInText(key,
								currentFile, method, false);
						if (contentResult.isFound) {
							searchResult.indexContentMap = new HashMap<String, Integer>(
									contentResult.indexContentMap);
							searchResult.isFound = true;
						}
						if (searchResult.isFound) {
							results.add(searchResult);
						}

					}
				}
			}

			if (currentFile.isDirectory()) {
				File[] candidates = currentFile.listFiles();
				for (File candidate : candidates) {
					queue.addLast(candidate);
				}
			}
		}

	}

	public static void findInDirectoryByDFS(String key, File file,
			boolean contentChecked, PatternMethod method,
			ArrayList<SearchResult> results, boolean fullSearch) {
		// search string in files in directory by DFS method
		Stack<File> queue = new Stack<File>();
		queue.push(file);

		while (!queue.isEmpty()) {
			File currentFile = queue.pop();
			// System.out.println(currentFile);
			if (currentFile.isFile()) {
				if (filterExtension(currentFile)) {
					if (!contentChecked) {
						// check only the title
						SearchResult searchResult = findKeysInTitle(key,
								currentFile, method);
						if (searchResult.isFound) {
							results.add(searchResult);
						}
					} else {
						// check title + content
						SearchResult searchResult = findKeysInTitle(key,
								currentFile, method);
						SearchResult contentResult = findKeysInText(key,
								currentFile, method, false);
						if (contentResult.isFound) {
							searchResult.indexContentMap = new HashMap<String, Integer>(
									contentResult.indexContentMap);
							searchResult.isFound = true;
						}
						if (searchResult.isFound) {
							results.add(searchResult);
						}

					}
				}
			}

			if (currentFile.isDirectory()) {
				if (currentFile.canRead()) {
					File[] candidates = currentFile.listFiles();
					if (candidates != null) {
						for (File candidate : candidates) {
							queue.push(candidate);
						}
					}
				}
			}
		}

	}

	public static String loadFileContent(File file) {
		// load the content of the file
		try {
			if (file.isFile()) {
				if (file.canRead()) {
					if (getExtension(file).equals("txt")){
						FileReader inputStream = new FileReader(file);
						int c;
						StringBuilder sb = new StringBuilder();
						while ((c = inputStream.read()) != -1) {
							sb.append((char) c);
						}
						inputStream.close();
						return sb.toString();
					}else if (getExtension(file).equals("doc")){
						FileInputStream fis = new FileInputStream(file);
						POIFSFileSystem poifs = new POIFSFileSystem(fis);
						WordExtractor wordExtractor = new WordExtractor(poifs);						
						return wordExtractor.getText();
					}else if (getExtension(file).equals("docx")){
						FileInputStream fis = new FileInputStream(file);
						XWPFDocument document = new XWPFDocument(fis);
						XWPFWordExtractor wordExtractor = new XWPFWordExtractor(document);	
						return wordExtractor.getText();
					}else if (getExtension(file).equals("xls")){
						FileInputStream fis = new FileInputStream(file);
						POIFSFileSystem poifs = new POIFSFileSystem(fis);
						ExcelExtractor wordExtractor = new ExcelExtractor(poifs);			
						return wordExtractor.getText();
					}else if (getExtension(file).equals("xlsx")){
						FileInputStream fis = new FileInputStream(file);						
						XSSFWorkbook document = new XSSFWorkbook(fis);
						XSSFExcelExtractor wordExtractor = new XSSFExcelExtractor(document);							
						return wordExtractor.getText();
					}
					else{
						FileReader inputStream = new FileReader(file);
						int c;
						StringBuilder sb = new StringBuilder();
						while ((c = inputStream.read()) != -1) {
							sb.append((char) c);
						}
						inputStream.close();
						return sb.toString();
					}
					
				}
			}

		} catch (Exception e) {
			return "";
		}
		return "";

	}

	public static String getExtension(File file) {
		// get the extension
		if (file.isFile()) {
			int dotIndex = file.toString().lastIndexOf(".");
			String extension = file.toString().substring(dotIndex + 1,
					file.toString().length());
			return extension;
		} else {
			return "";
		}
	}

	public static String getFileName(File file) {
		// get file name
		if (file.isFile()) {
			int dotIndex = file.toString().lastIndexOf(".");
			int fileSeparatorIndex = file.toString()
					.lastIndexOf(File.separator);
			String fileName = file.toString().substring(fileSeparatorIndex + 1,
					dotIndex);
			return fileName;
		} else {
			return "";
		}

	}

	public static boolean filterExtension(File file) {
		String extension = getExtension(file);
		return extension.equalsIgnoreCase("txt")
				|| extension.equalsIgnoreCase("xls")
				|| extension.equalsIgnoreCase("xlsx")
		 || extension.equalsIgnoreCase("doc")
		 || extension.equalsIgnoreCase("docx")
		 || extension.equalsIgnoreCase("java")		 
		 || extension.equalsIgnoreCase("c")
		 || extension.equalsIgnoreCase("cpp")
		 || extension.equalsIgnoreCase("text")
		 || extension.equalsIgnoreCase("in")
		 || extension.equalsIgnoreCase("out")
		;

	}

	// mengembalikan list of index dari string yang ditemukan dalam pencarian
	public static ArrayList<Integer> findStringByKMP(String key, String string,
			boolean searchTillEnd) {
		// kalo search till end berarti sampai last occurence,
		ArrayList<Integer> indexOfResult = new ArrayList<Integer>();
		int[] borderFunction = calculateBorderFunction(key);
		int idxK = 1;
		int idxS = 0;
		int length = string.length();
		int keylength = key.length();
		boolean isFound = false;
		while (idxS < length && !isFound) {
			if (key.charAt(idxK - 1) == string.charAt(idxS)) {
				idxS++;
				idxK++;
				if (idxK == keylength + 1) {
					indexOfResult.add(idxS - keylength);

					if (!searchTillEnd) {
						isFound = true;
					} else {
						idxK = 1;
						idxS++;
					}
				}

			} else {
				if (idxK == 1) {
					idxS++;
				} else {
					if (idxK >= borderFunction.length) {
						idxK--;
					} else {
						idxK = borderFunction[idxK - 1] + 1;
					}
				}
			}

		}
		return indexOfResult;
	}

	// kembaliin index string terakhir yang ditemukan dalam pencarian

	public static int[] calculateBorderFunction(String key) {
		// border function for KMP algorithm
		int length = key.length();
		int[] borderFunction = new int[length - 1];

		int i = 1;
		borderFunction[0] = 0;
		while (i < length - 1) {
			String subkey = getPrefix(key, i + 1);
			int maxlength = 0;
			for (int j = 1; j <= i; j++) {
				if (getPrefix(subkey, j).equals(getSuffix(subkey, j))) {
					if (maxlength < j) {
						maxlength = j;
					}
				}
			}
			borderFunction[i] = maxlength;
			i++;
		}
		return borderFunction;
	}

	public static String getPrefix(String string, int size) {
		return string.substring(0, size);
	}

	public static String getSuffix(String string, int size) {
		return string.substring(string.length() - size, string.length());
	}

	// kembaliin -1 kalo ga ada, kalo ada kembaliin index ketemunya dimana
	// (index yang dikembaliin adalah index huruf pertama
	public static int findStringByBoyerMoore(String pattern, String text) {
		// System.out.println(pattern+"="+pattern.length()+"|"+text+"="+text.length());
		int last[] = lastOccurence(pattern);
		int n = text.length();
		int m = pattern.length();
		int i = m - 1;

		if (i > n - 1)
			return -1; // no match if pattern is
		// longer than text
		int j = m - 1;
		do {
			if (pattern.charAt(j) == text.charAt(i))
				if (j == 0)
					return i;
				// match
				else { // looking-glass technique
					i--;
					j--;
				}
			else { // character jump technique
				int lo = last[text.charAt(i)]; // last occ
				i = i + m - Math.min(j, 1 + lo);
				j = m - 1;
			}
		} while (i <= n - 1);
		return -1; // no match
	}

	public static int[] lastOccurence(String pattern) {
		int last[] = new int[128];
		// ASCII char set
		for (int i = 0; i < 128; i++)
			last[i] = -1; // initialize array
		for (int i = 0; i < pattern.length(); i++)
			last[pattern.charAt(i)] = i;
		return last;
		// end of buildLast()

	}

	public static SearchResult findKeysInText(String keys, File file,
			PatternMethod method, boolean fullSearch) {

		// finding keys in text
		SearchResult result = new SearchResult(file);
		String text = loadFileContent(file).toLowerCase();
		try{
			if (method == PatternMethod.KMP) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					ArrayList<Integer> idxResult = findStringByKMP(temp, text,
							fullSearch);
					if (!idxResult.isEmpty()) {
						result.isFound = true;
						result.indexContentMap.put(temp, idxResult.get(0));
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						ArrayList<Integer> idxResult = findStringByKMP(key, text,
								fullSearch);
						if (!idxResult.isEmpty()) {
							result.isFound = true;
							result.indexContentMap.put(key, idxResult.get(0));
						}
					}
				}
			} else if (method == PatternMethod.BM) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					int idxResult = findStringByBoyerMoore(temp, text);
					if (idxResult != -1) {
						result.isFound = true;
						result.indexContentMap.put(temp, idxResult);
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						int idxResult = findStringByBoyerMoore(key, text);
						if (idxResult != -1) {
							result.isFound = true;
							result.indexContentMap.put(key, idxResult);
						}
					}
				}
	
			} else if (method == PatternMethod.JAVA) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					int idxResult = text.indexOf(temp);
					if (idxResult != -1) {
						result.isFound = true;
						result.indexContentMap.put(temp, idxResult);
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						int idxResult = text.indexOf(key);
						if (idxResult != -1) {
							result.isFound = true;
							result.indexContentMap.put(key, idxResult);
						}
					}
				}
			}
			 else if (method == PatternMethod.RK) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					int idxResult = RKSearch(temp, text);
					if (idxResult != -1) {
						result.isFound = true;
						result.indexContentMap.put(temp, idxResult);
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						int idxResult = RKSearch(key, text);
						if (idxResult != -1) {
							result.isFound = true;
							result.indexContentMap.put(key, idxResult);
						}
					}
				}
			}
		}catch(Exception e){
			
		}

		return result;
	}

	public static SearchResult findKeysInTitle(String keys, File file,
			PatternMethod method) {
		// finding keys in title

		SearchResult result = new SearchResult(file);
		String title = getFileName(file).toLowerCase();
		try{
			if (method == PatternMethod.KMP) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					ArrayList<Integer> idxResult = findStringByKMP(temp, title,
							false);
					if (!idxResult.isEmpty()) {
						result.isFound = true;
						result.indexTitleMap.put(temp, idxResult.get(0));
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						ArrayList<Integer> idxResult = findStringByKMP(key, title,
								false);
						if (!idxResult.isEmpty()) {
							result.isFound = true;
							result.indexTitleMap.put(key, idxResult.get(0));
						}
					}
				}
			} else if (method == PatternMethod.BM) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					int idxResult = findStringByBoyerMoore(temp, title);
					if (idxResult != -1) {
						result.isFound = true;
						result.indexTitleMap.put(temp, idxResult);
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						int idxResult = findStringByBoyerMoore(key, title);
						if (idxResult != -1) {
							result.isFound = true;
							result.indexTitleMap.put(key, idxResult);
						}
					}
				}
	
			} else if (method == PatternMethod.JAVA) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					int idxResult = title.indexOf(temp);
					if (idxResult != -1) {
						result.isFound = true;
						result.indexTitleMap.put(temp, idxResult);
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						int idxResult = title.indexOf(key);
						if (idxResult != -1) {
							result.isFound = true;
							result.indexTitleMap.put(key, idxResult);
						}
					}
				}
			}else if (method == PatternMethod.RK) {
				StringTokenizer tokenizer = new StringTokenizer(keys,
						" .,~!@#$%^&*()");
				if (tokenizer.countTokens() == 1) {
					String temp = tokenizer.nextToken();
					int idxResult = RKSearch(temp, title);
					if (idxResult != -1) {
						result.isFound = true;
						result.indexTitleMap.put(temp, idxResult);
					}
				} else {
					while (tokenizer.hasMoreTokens()) {
						String key = tokenizer.nextToken();
						int idxResult = RKSearch(key, title);
						if (idxResult != -1) {
							result.isFound = true;
							result.indexTitleMap.put(key, idxResult);
						}
					}
				}
			}
		}catch (Exception e){
			
		}
		

		return result;
	}

	public static boolean filterKeySearch(String key) {
		return !ignoredWords.containsKey(key.toLowerCase());
	}
	
	public static void setPreview (SearchResult searchResult){
		StringBuffer sb = new StringBuffer(loadFileContent(searchResult.file));
		
		int keyNumber= searchResult.indexContentMap.size();
		if (keyNumber>0){
			String [] arrayOfKeys = new String[keyNumber];
			int  i = 0;
			Set<String> setOfKeys = searchResult.indexContentMap.keySet();
			Iterator<String> it = setOfKeys.iterator();
			for ( it = setOfKeys.iterator() ; it.hasNext();){
				arrayOfKeys[i] = it.next();
				i++;
			}			
			StringBuilder preview = new StringBuilder();
			
			if (sb.length()>=160){
				for (int j = 0; j< arrayOfKeys.length; j++){
					if (preview.indexOf(arrayOfKeys[j])==-1){
						int fidx = sb.indexOf(arrayOfKeys[j]);
						int lidx = fidx + arrayOfKeys[j].length() ;
						if (lidx >= sb.length()){
							// kalo last > length
							lidx = sb.length();
							if (fidx - (160 / keyNumber) <0 ){
								// first < 0
								fidx = 0;							
									preview.append(sb.substring(fidx, lidx));											
							}else{
								// first > 0
								fidx -= (160/keyNumber);
								preview.append("..."+sb.substring(fidx, lidx));
							}									
						}else{
							// last < length
							if (fidx - (160 / keyNumber) <0 ){
								// first < 0
								fidx = 0;
								preview.append(sb.substring(fidx, lidx));
							}else{
								// first > 0
								fidx -= (160/keyNumber);
								preview.append(sb.substring(fidx, lidx)+"...");
							}											
						}
						preview.append("...");
					}
				}
			}else{
				preview.append(sb);
			}
			
			for (int j = 0; j< arrayOfKeys.length;j++){
				int fidx = preview.indexOf(arrayOfKeys[j]);						
				if (fidx !=-1){
					preview.insert(fidx, "<b>");
					int lidx = preview.indexOf(arrayOfKeys[j]) + arrayOfKeys[j].length();
			        preview.insert(lidx, "</b>");	        
				}
			}
			
			searchResult.preview = preview.toString();					
		}
	}
	
	public static int RKSearch(String Pattern,String SearchedText)
	{
		if((SearchedText.length() ==0 ) || (Pattern.length() ==0 )) //if the string is not legal, return -1
		{
			return -1;
		}
		
		int Patternlength = Pattern.length(); //set the length of the pattern in patternlength variable
		
		//iterate the searched text
		for(int i =0;i<SearchedText.length()-Patternlength;i++)
		{
			if( SearchedText.substring(i,i+Patternlength).hashCode() ==  Pattern.hashCode()) //compare the hash code
			{
				return i;
			}
		}
		return -1;
		
	}
}
